/**
 * 
 */
package com.laozhao.dreamframe.view;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.swing.event.EventListenerList;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;

import com.laozhao.dreamframe.exception.ModelToTableException;
import com.laozhao.dreamframe.resources.DreamResource;
import com.laozhao.dreamframe.view.DreamTableModeMeta.Status;

/**
 * version 1.0 仅能只读Model，可以通过更新Model去更新Table，无法通过更新Table来反向更新Model。 version 2.0
 * 读Model，可以通过更新Model去更新Table，可以通过更新Table来反向更新Model。
 * 
 * @author LaoZhao
 * @version 2.0
 * 
 */
public class DreamTableModel<T extends DreamTableModeMeta> extends
		AbstractTableModel {

	/**
	 * 
	 */
	private static final long serialVersionUID = -8197070646061000085L;

	private String[] columnNames;
	private boolean[] columnHide;
	private boolean[] columnEditable;
	private Class<?>[] columnClass;

	// private Object[][] data;

	private List<T> modeList;
	private Method[] setMethods;
	private Method[] getMethods;

	// 内部匿名Field List
	// List<Field> fieldList;
	// 内部匿名model
	Class<T> clazz;
	//
	DreamResource source;

	//

	public DreamTableModel(Class<T> clazz) {
		this.source = new DreamResource();
		// columnNames = new String[] { "" };
		// columnHide = new boolean[]{false};
		// columnEditable = new boolean[]{false};
		// columnClass = new Class<?>[]{ String.class };
		// data = new Object[][] { new String[] { "" } };
		this.clazz = clazz;
		this.modeList = new LinkedList<T>();
		initModel();
	}

	// /**
	// *
	// */
	// public DreamTableModel(List<T> list) {
	// source = new DreamResource();
	// this.modeList = list;
	// columnNames = new String[] { "" };
	// columnHide = new boolean[]{false};
	// columnEditable = new boolean[]{false};
	// columnClass = new Class<?>[]{ String.class };
	// data = new Object[][] { new String[] { "" } };
	// // this.clazz = T;
	// initModel();
	// }

	/**
	 * 
	 */
	public DreamTableModel(List<T> list, Class<T> clazz) {
		this.source = new DreamResource();
		this.modeList = list;
		this.clazz = clazz;
		initModel();
	}

	private void initModel() {
		if (clazz != null) {
			try {
				List<Field> fieldList = new ArrayList<Field>();
				Class<?> clazzInfo = clazz;
				// for(clazzInfo = clazz ; ; ){
				//
				// }
				do {
					Field[] fields = clazzInfo.getDeclaredFields();
					for (Field field : fields) {
						if (field.getAnnotation(DreamUIColumn.class) != null) {
							fieldList.add(field);
						}
					}
					clazzInfo = clazzInfo.getSuperclass();
					
				} while (!clazzInfo.getName().equals(Object.class.getName()));
				columnNames = new String[fieldList.size()];
				columnHide = new boolean[fieldList.size()];
				columnEditable = new boolean[fieldList.size()];
				columnClass = new Class[fieldList.size()];

				// if (modeList == null) {
				// data = new Object[0][fieldList.size()];
				// } else {
				// data = new Object[modeList.size()][fieldList.size()];
				// }
				setMethods = new Method[fieldList.size()];
				getMethods = new Method[fieldList.size()];

				for (Field field : fieldList) {
					DreamUIColumn column = field
							.getAnnotation(DreamUIColumn.class);
					if (column.uiColumnHide()) {
						columnNames[column.uiColumnIndex()] = "";
					} else {
						columnNames[column.uiColumnIndex()] = source
								.getString(column.uiColumnTitle());
					}
					columnHide[column.uiColumnIndex()] = column.uiColumnHide();
					columnEditable[column.uiColumnIndex()] = column
							.columnEditable();
					columnClass[column.uiColumnIndex()] = field.getType();

					// if (modeList != null) {
					// for (int j = 0; j < modeList.size(); j++) {
					// Method method = clazz.getMethod("get"+
					// field.getName().substring(0, 1).toUpperCase()
					// + field.getName().substring(1,field.getName().length()),
					// null);
					//
					// data[j][column.uiColumnIndex()] =
					// method.invoke(modeList.get(j), null);
					// }
					// }
					setMethods[column.uiColumnIndex()] = clazz.getMethod(
							"set"
									+ field.getName().substring(0, 1)
											.toUpperCase()
									+ field.getName().substring(1,
											field.getName().length()),
							field.getType());
					getMethods[column.uiColumnIndex()] = clazz.getMethod("get"
							+ field.getName().substring(0, 1).toUpperCase()
							+ field.getName().substring(1,
									field.getName().length()));
				}
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
				throw new ModelToTableException(e);
				// } catch (IllegalAccessException e) {
				// e.printStackTrace();
				// throw new ModelToTableException(e);
				// } catch (InvocationTargetException e) {
				// e.printStackTrace();
				// throw new ModelToTableException(e);
			} catch (SecurityException e) {
				e.printStackTrace();
				throw new ModelToTableException(e);
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
				throw new ModelToTableException(e);
			}
		} else {
			columnNames = new String[] { "" };
			columnHide = new boolean[] { false };
			columnEditable = new boolean[] { false };
			columnClass = new Class<?>[] { String.class };
			// data = new Object[][] { new String[] { "" } };
			setMethods = new Method[] { null };
			getMethods = new Method[] { null };

		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.table.TableModel#getColumnCount()
	 */
	@Override
	public int getColumnCount() {
		return columnNames.length;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.table.TableModel#getRowCount()
	 */
	@Override
	public int getRowCount() {
		// return data.length;
		return modeList.size();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.table.TableModel#getValueAt(int, int)
	 */
	@Override
	public Object getValueAt(int rowIndex, int columnIndex) {
		try {
			return getMethods[columnIndex].invoke(modeList.get(rowIndex));
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		return null;
		// InvocationHandler();
		// if (rowIndex < data.length && columnIndex < columnNames.length) {
		// return data[rowIndex][columnIndex];
		// } else {
		// return "";
		// }
	}

	public String getColumnName(int col) {
		return columnNames[col];
	}

	/*
	 * JTable uses this method to determine the default renderer/ editor for
	 * each cell. If we didn't implement this method, then the last column would
	 * contain text ("true"/"false"), rather than a check box.
	 */
	@Override
	public Class<?> getColumnClass(int c) {
		if (c < columnClass.length) {
			return columnClass[c];
		} else {
			return null;
		}
	}

	/*
	 * Don't need to implement this method unless your table's editable.
	 * 以@DreamModel中定义的列的只读为优先
	 */
	@Override
	public boolean isCellEditable(int row, int col) {
		if (col < columnEditable.length) {
			// if (!columnEditable[col]) {
			// return false;
			// }else {
			//
			// }
			return columnEditable[col];
		}
		return false;
	}

	/*
	 * Don't need to implement this method unless your table's data can change.
	 */
	public void setValueAt(Object value, int rowIndex, int columnIndex) {
		T model = modeList.get(columnIndex);
		try {
			setMethods[columnIndex].invoke(model, value);
			model.setStatus(Status.modify);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}

		model.setStatus(Status.modify);
		fireTableCellUpdated(rowIndex, columnIndex);
		// if (rowIndex < data.length && columnIndex < columnNames.length) {
		// data[rowIndex][columnIndex] = value;
		// // fireTableCellUpdated(rowIndex, columnIndex);
		// } else {
		// throw new ModelToTableException();
		// }
	}

	public boolean isColumnHide(int columnIndex) {
		if (columnIndex < columnHide.length) {
			return columnHide[columnIndex];
		} else {
			return false;
		}
	}

	public void setRowModelData(T modelData, int row) {
		int rowCount = getRowCount();
		modeList.set(row, modelData);
		modelData.setStatus(Status.modify);
		fireTableRowsUpdated(rowCount, rowCount);
		
		// if (clazz != null && fieldList != null) {
		// Object[] newModelData = new Object[fieldList.size()];
		// try {
		// for (Field field : fieldList) {
		// DreamUIColumn column = field
		// .getAnnotation(DreamUIColumn.class);
		// Method method = clazz.getMethod("get"
		// + field.getName().substring(0, 1).toUpperCase()
		// + field.getName().substring(1,
		// field.getName().length()), null);
		// newModelData[column.uiColumnIndex()] = method.invoke(
		// modeList.get(row), null);
		// }
		// } catch (SecurityException e) {
		// e.printStackTrace();
		// throw new ModelToTableException();
		// } catch (NoSuchMethodException e) {
		// e.printStackTrace();
		// throw new ModelToTableException();
		// } catch (IllegalAccessException e) {
		// e.printStackTrace();
		// throw new ModelToTableException();
		// } catch (IllegalArgumentException e) {
		// e.printStackTrace();
		// throw new ModelToTableException();
		// } catch (InvocationTargetException e) {
		// e.printStackTrace();
		// throw new ModelToTableException();
		// }
		// if no error ,set value
		// data[row] = newModelData;
		// }
	}

	public DreamTableModeMeta getRowModelData(int row) {
		return modeList.get(row);
	}

	// public Object[] getRowData(int row) {
	// return data[row];
	// }

	public List<T> getModeList() {
		return modeList;
	}

	public void setModeList(List<T> modeList) {
		this.modeList = modeList;
		initModel();
	}

	public Class<T> getModelClassName() {
		return clazz;
	}

	public void addRow(T model) {
		int rowCount = getRowCount();
		modeList.add(model);
		model.setStatus(Status.create);
		fireTableRowsInserted(rowCount, rowCount);
	}

	// public void delRow(int rowIndex){
	// modeList.remove(rowIndex);
	// }
	// public void setModelClassName(List<? extends AbstractModel> modeList,
	// Class<? extends AbstractModel> clazz) {
	// this.clazz = clazz;
	// this.modeList = modeList;
	// initModel();
	// }

	//
	// Fire methods
	//

	/**
	 * Notifies all listeners that all cell values in the table's rows may have
	 * changed. The number of rows may also have changed and the
	 * <code>JTable</code> should redraw the table from scratch. The structure
	 * of the table (as in the order of the columns) is assumed to be the same.
	 * 
	 * @see TableModelEvent
	 * @see EventListenerList
	 * @see javax.swing.JTable#tableChanged(TableModelEvent)
	 */
	public void fireTableDataChanged() {
		fireTableChanged(new TableModelEvent(this));
	}

	/**
	 * Notifies all listeners that the table's structure has changed. The number
	 * of columns in the table, and the names and types of the new columns may
	 * be different from the previous state. If the <code>JTable</code> receives
	 * this event and its <code>autoCreateColumnsFromModel</code> flag is set it
	 * discards any table columns that it had and reallocates default columns in
	 * the order they appear in the model. This is the same as calling
	 * <code>setModel(TableModel)</code> on the <code>JTable</code>.
	 * 
	 * @see TableModelEvent
	 * @see EventListenerList
	 */
	public void fireTableStructureChanged() {
		fireTableChanged(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
	}

	/**
	 * Notifies all listeners that rows in the range
	 * <code>[firstRow, lastRow]</code>, inclusive, have been inserted.
	 * 
	 * @param firstRow
	 *            the first row
	 * @param lastRow
	 *            the last row
	 * 
	 * @see TableModelEvent
	 * @see EventListenerList
	 * 
	 */
	public void fireTableRowsInserted(int firstRow, int lastRow) {
		fireTableChanged(new TableModelEvent(this, firstRow, lastRow,
				TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT));
	}

	/**
	 * Notifies all listeners that rows in the range
	 * <code>[firstRow, lastRow]</code>, inclusive, have been updated.
	 * 
	 * @param firstRow
	 *            the first row
	 * @param lastRow
	 *            the last row
	 * 
	 * @see TableModelEvent
	 * @see EventListenerList
	 */
	public void fireTableRowsUpdated(int firstRow, int lastRow) {
		fireTableChanged(new TableModelEvent(this, firstRow, lastRow,
				TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE));
	}

	/**
	 * Notifies all listeners that rows in the range
	 * <code>[firstRow, lastRow]</code>, inclusive, have been deleted.
	 * 
	 * @param firstRow
	 *            the first row
	 * @param lastRow
	 *            the last row
	 * 
	 * @see TableModelEvent
	 * @see EventListenerList
	 */
	public void fireTableRowsDeleted(int firstRow, int lastRow) {
		fireTableChanged(new TableModelEvent(this, firstRow, lastRow,
				TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE));
	}

	/**
	 * Notifies all listeners that the value of the cell at
	 * <code>[row, column]</code> has been updated.
	 * 
	 * @param row
	 *            row of cell which has been updated
	 * @param column
	 *            column of cell which has been updated
	 * @see TableModelEvent
	 * @see EventListenerList
	 */
	public void fireTableCellUpdated(int row, int column) {
		fireTableChanged(new TableModelEvent(this, row, row, column));
	}

	/**
	 * Forwards the given notification event to all
	 * <code>TableModelListeners</code> that registered themselves as listeners
	 * for this table model.
	 * 
	 * @param e
	 *            the event to be forwarded
	 * 
	 * @see #addTableModelListener
	 * @see TableModelEvent
	 * @see EventListenerList
	 */
	public void fireTableChanged(TableModelEvent e) {
		// Guaranteed to return a non-null array
		Object[] listeners = listenerList.getListenerList();
		// Process the listeners last to first, notifying
		// those that are interested in this event
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == TableModelListener.class) {
				((TableModelListener) listeners[i + 1]).tableChanged(e);
			}
		}
	}
}
